﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace AjaxControlFramework
{
    public abstract class StateStrategy : PageStatePersister, IAjaxControlStrategy
    {
        //------// Properties \\--------------------------------------------\\
        public virtual IAjaxControl Control { get; set; }

        private Control _targetControl = null;
        public virtual Control TargetControl
        {
            get
            {
                if (_targetControl == null)
                {
                    _targetControl = Control as Control;
                }

                return _targetControl;
            }
            set
            {
                _targetControl = value;
            }
        }


        protected virtual List<IAjaxControl> ChildAjaxControls { get; set; }

        public virtual bool StateLoaded { get; set; }
        public virtual bool StateSaved { get; set; }


        private static Type TypeOfControl = typeof(System.Web.UI.Control);



        public delegate object SaveViewStateMethod(Control targetControl, ViewStateMode viewStateMode);

        private static object SaveViewStateRecursiveLock = new object();
        private static SaveViewStateMethod _saveViewStateRecursive = null;
        public static SaveViewStateMethod SaveViewStateRecursive
        {
            get
            {
                if (_saveViewStateRecursive == null)
                {
                    lock (SaveViewStateRecursiveLock)
                    {
                        if (_saveViewStateRecursive == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "SaveViewStateRecursive".
                            MethodInfo saveViewStateRecursiveMethodInfo = TypeOfControl.GetMethod("SaveViewStateRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { typeof(ViewStateMode) }, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("SaveViewStateRecursive", typeof(object), new Type[] { TypeOfControl, typeof(ViewStateMode) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Call, saveViewStateRecursiveMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _saveViewStateRecursive = (SaveViewStateMethod)dynamicMethod.CreateDelegate(typeof(SaveViewStateMethod));
                        }
                    }
                }

                return _saveViewStateRecursive;
            }
        }


        public delegate object SaveControlStateMethod(Control targetControl);

        private static object SaveControlStateInternalLock = new object();
        private static SaveControlStateMethod _saveControlStateInternal = null;
        public static SaveControlStateMethod SaveControlStateInternal
        {
            get
            {
                if (_saveControlStateInternal == null)
                {
                    lock (SaveControlStateInternalLock)
                    {
                        if (_saveControlStateInternal == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "SaveControlStateInternal".
                            MethodInfo saveControlStateInternalMethodInfo = TypeOfControl.GetMethod("SaveControlStateInternal", (BindingFlags.NonPublic | BindingFlags.Instance), null, Type.EmptyTypes, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("SaveControlStateInternal", typeof(object), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Call, saveControlStateInternalMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _saveControlStateInternal = (SaveControlStateMethod)dynamicMethod.CreateDelegate(typeof(SaveControlStateMethod));
                        }
                    }
                }

                return _saveControlStateInternal;
            }
        }


        public delegate void LoadStateMethod(Control targetControl, object state);

        private static object LoadViewStateRecursiveLock = new object();
        private static LoadStateMethod _loadViewStateRecursive = null;
        public static LoadStateMethod LoadViewStateRecursive
        {
            get
            {
                if (_loadViewStateRecursive == null)
                {
                    lock (LoadViewStateRecursiveLock)
                    {
                        if (_loadViewStateRecursive == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "LoadViewStateRecursive".
                            MethodInfo loadViewStateRecursiveMethodInfo = TypeOfControl.GetMethod("LoadViewStateRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { typeof(object) }, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("LoadViewStateRecursive", typeof(void), new Type[] { TypeOfControl, typeof(object) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Call, loadViewStateRecursiveMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _loadViewStateRecursive = (LoadStateMethod)dynamicMethod.CreateDelegate(typeof(LoadStateMethod));
                        }
                    }
                }

                return _loadViewStateRecursive;
            }
        }


        private static object LoadControlStateInternalLock = new object();
        private static LoadStateMethod _loadControlStateInternal = null;
        public static LoadStateMethod LoadControlStateInternal
        {
            get
            {
                if (_loadControlStateInternal == null)
                {
                    lock (LoadControlStateInternalLock)
                    {
                        if (_loadControlStateInternal == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "LoadControlStateInternal".
                            MethodInfo loadControlStateInternalMethodInfo = TypeOfControl.GetMethod("LoadControlStateInternal", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { typeof(object) }, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("LoadControlStateInternal", typeof(void), new Type[] { TypeOfControl, typeof(object) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Call, loadControlStateInternalMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _loadControlStateInternal = (LoadStateMethod)dynamicMethod.CreateDelegate(typeof(LoadStateMethod));
                        }
                    }
                }

                return _loadControlStateInternal;
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Constructors \\------------------------------------------\\
        protected StateStrategy(Control control)
            : base(control is Page ? (Page)control : (HttpContext.Current.CurrentHandler is Page ? (Page)HttpContext.Current.CurrentHandler : (Page)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CurrentPage"]))
        {
            if (control == null) { throw new ArgumentNullException("control"); }
            if (!(control is IAjaxControl)) { throw new ArgumentException("A reference of type IAjaxControl was expected.", "control"); }

            Control = control as IAjaxControl;
        }
        //------\\ Constructors //------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public virtual void Save(bool saveStateOfChildren)
        {
            if (saveStateOfChildren)
            {
                if (ChildAjaxControls != null && ChildAjaxControls.Count > 0)
                {
                    foreach (IAjaxControl ajaxControl in ChildAjaxControls)
                    {
                        if (ajaxControl.StateStrategy != null && ajaxControl.StateStrategy.StateLoaded)
                        {
                            ajaxControl.StateStrategy.Save(false);
                        }
                    }
                }
            }


            // Retrieve the ViewState and ControlState objects returned by the SaveState delegates.
            ControlState = SaveControlStateInternal(TargetControl);
            ViewState = SaveViewStateRecursive(TargetControl, (TargetControl.ViewStateMode == ViewStateMode.Inherit ? ViewStateMode.Enabled : TargetControl.ViewStateMode));

            PersistState();

            StateSaved = true;
        }


        // This is a base implementation and is intended to be overriden.
        public override void Save()
        {
            Save(true);
        }


        public virtual void Load(bool loadStateOfChildren)
        {
            if (loadStateOfChildren)
            {
                ChildAjaxControls = AjaxControlUtils.FindChildControlsByType<IAjaxControl>(TargetControl);

                if (ChildAjaxControls != null)
                {
                    foreach (IAjaxControl ajaxControl in ChildAjaxControls)
                    {
                        if (ajaxControl.StateStrategy != null)
                        {
                            Control childControl = (ajaxControl as Control);

                            if (childControl != null && childControl.EnableViewState)
                            {
                                ajaxControl.StateStrategy.Load(false);
                                childControl.EnableViewState = false;
                            }
                        }
                    }
                }
            }


            RestoreState();


            // Retrieve the ViewState and ControlState objects returned by the SaveState delegates.
            if (ViewState != null || ControlState != null)
            {
                LoadControlStateInternal(TargetControl, ControlState);
                LoadViewStateRecursive(TargetControl, ViewState);

                StateLoaded = true;
            }
            else
            {
                StateLoaded = false;
            }
        }


        // This is a base implementation and is intended to be overriden.
        public override void Load()
        {
            Load(true);
        }


        /// <summary>
        /// Returns the Control that will be targeted for ViewState/ControlState persistence and restoration.
        /// </summary>
        /// <returns>The purpose of this method is give a helping hand to developers who are nesting IAjaxControl's 
        /// within DataBound WebControls. The logic of this method detects if the actual target IAjaxControl is an 
        /// child of an item of a data bound control. If it is, the NamingContainer of the IAjaxControl is 
        /// considered the target for ViewState/ControlState persistence.</returns>
        protected virtual Control DetermineTargetControl()
        {
            Control targetControl = TargetControl;

            // DataBoundControl --> AdRotator, ListControl, ListView, CompositeDataBoundControl
            // CompositeDataBoundControl --> DetailsView, FormView, GridView
            // IDataItemContainer --> DataGridItem, DataListItem, ListViewDataItem, RepeaterItem, SiteMapNodeItem, MenuItemTemplateContainer, GridViewRow, DetailsView, FormView

            if (targetControl.NamingContainer is DataBoundControl)
            {
                targetControl = targetControl.NamingContainer;
            }
            else if (targetControl.NamingContainer is IDataItemContainer)
            {
                targetControl = targetControl.NamingContainer.NamingContainer;
            }

            return targetControl;
        }


        protected virtual void PersistState()
        {
            if (base.ViewState != null || base.ControlState != null)
            {
                // Serialize both the page ControlState and ViewState.
                PersistStateToMedium(base.StateFormatter.Serialize(new Pair(base.ViewState, base.ControlState)));
            }
        }


        protected virtual void RestoreState()
        {
            Pair statePair = RestoreStateFromMedium();

            if (statePair != null)
            {
                base.ViewState = statePair.First;
                base.ControlState = statePair.Second;
            }
        }


        protected abstract void PersistStateToMedium(string state);
        protected abstract Pair RestoreStateFromMedium();
        //------\\ Methods //-----------------------------------------------//
    }
}
